#include <tinx/device.h>
#include <tinx/interrupt.h>
#include <tinx/io.h>
#include <tinx/memory.h>
#include <tinx/fb.h>
#include <tinx/device.h>
#include <tinx/fs.h>
#include <tinx/arena.h>
#include <tinx/syscall.h>
#include <tinx/string.h>
#include <tinx/psf.h>

#define NUL 0x00
#define ENQ 0x05
#define ESC 0x1B // ESC
#define BEL 0x07 // \a
#define BS 0x08  // \b
#define HT 0x09  // \t
#define LF 0x0A  // \n
#define VT 0x0B  // \v
#define FF 0x0C  // \f
#define CR 0x0D  // \r
#define DEL 0x7F

#define RGBA_GREY 0x00808080

typedef struct color_t
{
    u8 a;
    u8 b;
    u8 g;
    u8 r;
} _packed color_t;

typedef struct console_t
{
    u32 x, y;

    u32 row_chars;
    u32 scr_chars;

    u32 row_size;
    u32 scr_size;
    u32 char_size;

    u32 screen;
    u32 mem_base;
    u32 mem_size;
    u32 mem_end;

    color_t style;

    // 字符数
    u32 width;
    u32 height;

    psf1_header_t *font;
} console_t;

extern framebuffer_t *fb;
static console_t fbcon;

static void fb_chr(console_t *con, u32 x, u32 y, char c)
{
    psf1_header_t *header = con->font;
    u32 *ptr = (u32 *)con->mem_base + y * fb->width * header->height + x * 8;

    u8 *line = (u8 *)((u32)header + sizeof(psf1_header_t) + c * header->height);

    for (int i = 0; i < header->height; i++)
    {
        for (int j = 0; j < 8; j++)
        {
            ptr[j] = line[i] & (1 << (8 - j)) ? *(u32 *)&con->style : 0;
        }
        ptr += fb->width;
    }
}

static void scroll_up(console_t *con)
{
    memcpy((void *)con->mem_base, (void *)(con->mem_base + con->row_size), con->mem_size - con->row_size);
    memset((void *)(con->mem_end - con->row_size), 0, con->row_size);
}

inline static void cr(console_t *con)
{
    con->x = 0;
}

inline static void lf(console_t *con)
{
    if (con->y + 1 >= con->height)
    {
        scroll_up(con);
    }
    else
    {
        con->y++;
    }
}

inline static void chr(console_t *con, char ch)
{
    fb_chr(con, con->x, con->y, ch);
    con->x++;
    if (con->x >= con->row_chars)
    {
        lf(con);
        cr(con);
    }
}

inline static void bs(console_t *con)
{
    if (!con->x && !con->y)
        return; // 暂时不支持
    if (!con->x)
    {
        con->x = con->row_chars;
        con->y--;
    }
    con->x--;
    fb_chr(con, con->x, con->y, ' ');
}

int fbcon_write(console_t *con, char *data, int count)
{
    bool intr = interrupt_disable();

    int nr = 0;
    while (nr < count)
    {
        char ch = data[nr++];
        switch (ch)
        {
        case LF:
            lf(con);
        case CR:
            cr(con);
            break;
        case BS:
            bs(con);
            break;
        default:
            chr(con, ch);
            break;
        }
    }

    set_interrupt_state(intr);

    return nr;
}

static int font_init(console_t *con, char *file)
{
    inode_t *inode = namei(file);
    if (!inode)
        return -ENOENT;

    u8 *data = kmalloc(inode->size);
    assert(inode->op->read(inode, data, inode->size, 0) >= EOK);

    con->font = psf1_init(data);
    if (!con->font)
        return EOF;

    return EOK;
}

void fbcon_init()
{
    console_t *con = &fbcon;
    con->mem_base = fb->fb_vaddr;
    con->mem_size = fb->fb_len;
    con->mem_end = con->mem_base + con->mem_size;

    con->screen = con->mem_base;
    con->x = con->y = 0;

    con->style.a = 0xB0;
    con->style.g = 0xB0;
    con->style.r = 0xB0;
    con->style.b = 0xB0;

    assert(font_init(con, "/usr/fonts/LatArCyrHeb-16.psf") >= EOK);

    con->char_size = con->font->height * 8 * sizeof(color_t);
    con->row_chars = fb->width / 8;
    con->row_size = con->char_size * con->row_chars;

    con->width = con->row_chars;
    con->height = fb->height / con->font->height;

    dev_t dev = device_find(DEV_FLAG_CONSOLE, 1);
    device_t *device = device_get(dev);
    device->ptr = con;
    device->write = fbcon_write;
}